package zdyu.data_structures.linked_list;

import sun.reflect.generics.reflectiveObjects.NotImplementedException;

import java.lang.annotation.ElementType;
import java.util.*;
import java.util.stream.IntStream;

public class LinkedList<E> implements java.util.List<E> {

    private final Node<E> sentinel = new Node<>(null);
    {
        sentinel.next = sentinel;
        sentinel.prev = sentinel;
    }

    private int size = 0;

    @Override
    public int size() {
        return this.size;
    }

    @Override
    public boolean isEmpty() {
        return size() == 0;
    }

    @Override
    public boolean contains(Object o) {
        for (E e: this) {
            if (Objects.equals(o, e)) return true;
        }
        return false;
    }

    @Override
    public Iterator<E> iterator() {
        return this.listIterator();
    }

    @Override
    public synchronized Object[] toArray() {
        Object[] result = new Object[size()];

        final int[] index = {0};
        this.forEach(e -> {
            result[index[0]] = e;
            index[0] += 1;
        });

        return result;
    }

    @Override
    public <T> T[] toArray(T[] a) {
        // TODO: Implement
        throw new NotImplementedException();
    }

    @Override
    public synchronized boolean add(E e) {
        Node<E> node = new Node<>(e, sentinel.prev, sentinel);
        sentinel.prev.next = node;
        sentinel.prev = node;

        size += 1;

        return true;
    }

    @Override
    public synchronized boolean remove(Object o) {
        for (ListIterator<Node<E>> it = this.listNodeIterator(); it.hasNext(); ) {
            Node<E> node = it.next();

            if (Objects.equals(o, node.data)) {
                node.next.prev = node.prev;
                node.prev.next = node.next;

                node.next = null;
                node.prev = null;

                size -= 1;
                return true;
            }
        }

        return false;
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        for (Object e: c) {
            if (!contains(e)) return false;
        }

        return true;
    }

    @Override
    public synchronized boolean addAll(Collection<? extends E> c) {
        if (c.isEmpty()) return false;

        c.forEach(this::add);

        return true;
    }

    @Override
    public boolean addAll(int index, Collection<? extends E> c) {
        // TODO: Implement
        throw new NotImplementedException();
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        boolean result = false;

        for (Object e: c) {
            if (remove(e)) result = true;
        }

        return result;
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        // TODO: Implement
        throw new NotImplementedException();
    }

    @Override
    public synchronized void clear() {
        sentinel.prev = sentinel;
        sentinel.next = sentinel;

        size = 0;
    }

    @Override
    public synchronized E get(int index) throws IndexOutOfBoundsException {
        return getNode(index).data;
    }

    synchronized Node<E> getNode(int index) throws IndexOutOfBoundsException {
        checkBound(index);

        Node<E> result;
        ListIterator<Node<E>> iterator = this.listNodeIterator();

        if (isBetterReverse(index)) {
            IntStream.range(index, size()).forEach(i -> {
                iterator.previous();
            });
            result = iterator.previous();
        } else {
            IntStream.range(0, index).forEach(i -> {
                iterator.next();
            });
            result = iterator.next();
        }

        return result;
    }

    @Override
    public E set(int index, E element) {
        Node<E> node = getNode(index);

        E previous = node.data;

        node.data = element;

        return previous;
    }

    @Override
    public synchronized void add(int index, E element) {
        if (index == size()) add(element);

        Node<E> current = getNode(index);

        Node<E> newNode = new Node<>(element, current, current.next);

        current.next.prev = newNode;
        current.next = newNode;
    }

    @Override
    public synchronized E remove(int index) {
        Node<E> node = getNode(index);

        node.next.prev = node.prev;
        node.prev.next = node.next;

        node.prev = null;
        node.next = null;

        return node.data;
    }

    @Override
    public synchronized int indexOf(Object o) {
        int index = 0;

        for (E e: this) {
            if (Objects.equals(o, e)) return index;
            index += 1;
        }

        return -1;
    }

    @Override
    public synchronized int lastIndexOf(Object o) {
        int index = 0;
        int result = -1;

        for (E e: this) {
            if (Objects.equals(o, e)) return result = index;
            index += 1;
        }

        return result;
    }

    @Override
    public ListIterator<E> listIterator() {
        return new ListIterator<E>() {

            Node<E> current = sentinel;

            @Override
            public boolean hasNext() {
                return current.next != sentinel && current.next != null;
            }

            @Override
            public synchronized E next() {
                if (!hasNext()) throw new NoSuchElementException();

                current = current.next;

                return current.data;
            }

            @Override
            public boolean hasPrevious() {
                return current.prev != sentinel && current.next != null;
            }

            @Override
            public synchronized E previous() {
                if (!hasPrevious()) throw new NoSuchElementException();

                current = current.prev;

                return current.data;
            }

            @Override
            public int nextIndex() {
                // TODO: Implement
                throw new NotImplementedException();
            }

            @Override
            public int previousIndex() {
                // TODO: Implement
                throw new NotImplementedException();
            }

            @Override
            public void remove() {
                // TODO: Implement
                throw new NotImplementedException();
            }

            @Override
            public void set(E e) {
                // TODO: Implement
                throw new NotImplementedException();
            }

            @Override
            public void add(E e) {
                // TODO: Implement
                throw new NotImplementedException();
            }
        };
    }

    ListIterator<Node<E>> listNodeIterator() {
        return new ListIterator<Node<E>>() {

            Node<E> current = sentinel;

            @Override
            public boolean hasNext() {
                return current.next != sentinel && current.next != null;
            }

            @Override
            public synchronized Node<E> next() {
                if (!hasNext()) throw new NoSuchElementException();

                current = current.next;

                return current;
            }

            @Override
            public boolean hasPrevious() {
                return current.prev != sentinel && current.next != null;
            }

            @Override
            public synchronized Node<E> previous() {
                if (!hasPrevious()) throw new NoSuchElementException();

                current = current.prev;

                return current;
            }

            @Override
            public int nextIndex() {
                // TODO: Implement
                throw new NotImplementedException();
            }

            @Override
            public int previousIndex() {
                // TODO: Implement
                throw new NotImplementedException();
            }

            @Override
            public void remove() {
                // TODO: Implement
                throw new NotImplementedException();
            }

            @Override
            public void set(Node<E> eNode) {
                // TODO: Implement
                throw new NotImplementedException();
            }

            @Override
            public void add(Node<E> eNode) {
                // TODO: Implement
                throw new NotImplementedException();
            }
        };
    }

    @Override
    public ListIterator<E> listIterator(int index) {
        // TODO: Implement
        throw new NotImplementedException();    }

    @Override
    public List<E> subList(int fromIndex, int toIndex) {
        // TODO: Implement
        throw new NotImplementedException();    }

    boolean isBetterReverse(int index) {
        return index >= size();
    }


    void checkBound(int index) throws IndexOutOfBoundsException {
        if (!(0 <= index && index < size())) throw new IndexOutOfBoundsException();
    }

    @Override
    public String toString() {
        StringJoiner stringJoiner = new StringJoiner(", ", "[", "]");
        this.forEach(e -> {
            stringJoiner.add(e.toString());
        });
        return stringJoiner.toString();
    }

    static class Node<E> {

        E data = null;

        Node<E>
                prev = null,
                next = null;

        public Node(E data) {
            this.data = data;
        }
        public Node(E data, Node<E> prev, Node<E> next) {
            this.data = data;
            this.prev = prev;
            this.next = next;
        }

    }
}
